package org.oos.util;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * 
 * Cache implementation using an {@link AbstractMap} and {@link SoftReference}
 * 
 * @author pablo.gonzalez@11870.com
 * 
 */
public class Cache<Key, Type> extends AbstractMap<Key, Type> {

	private ReferenceQueue<Type> referenceQueue = new ReferenceQueue<Type>();

	private Set<Entry<Key, Type>> entrySet;
	private List<Entry<Key, Type>> entryList;

	private class CacheEntry extends SoftReference<Type> implements
			Entry<Key, Type> {

		private Key key;

		private CacheEntry(Key key, Type value) {
			super(value, referenceQueue);
			this.key = key;
		}

		@Override
		public Key getKey() {
			return key;
		}

		@Override
		public Type getValue() {
			return get();
		}

		@Override
		public Type setValue(Type value) {
			throw new UnsupportedOperationException();
		}

		@SuppressWarnings("unchecked")
		@Override
		public boolean equals(Object o) {
			if (!(o instanceof Entry<?, ?>)) {
				return false;
			}
			Entry<Key, Type> e = (Entry<Key, Type>) o;
			Object value = get();
			return (key == null ? e.getKey() == null : key.equals(e.getKey()))
					&& (value == null ? e.getValue() == null : value.equals(e
							.getValue()));
		}

		public int hashCode() {
			int keyHash = (key == null ? 0 : key.hashCode());
			Object value = get();
			int valueHash = (value == null ? 0 : value.hashCode());
			return keyHash ^ valueHash;
		}

	}

	@Override
	public Set<Entry<Key, Type>> entrySet() {
		if (entryList == null) {
			entryList = new ArrayList<Entry<Key, Type>>();
		}
		if (entrySet == null) {
			entrySet = new AbstractSet<Entry<Key, Type>>() {

				@Override
				public void clear() {
					entryList.clear();
				}

				@Override
				public Iterator<Entry<Key, Type>> iterator() {
					return entryList.iterator();
				}

				@Override
				public int size() {
					return entryList.size();
				}

			};
		}
		return entrySet;
	}

	@Override
	public synchronized Type put(Key key, Type value) {
		if (value == null) {
			return null;
		}

		if (entryList == null) {
			entryList = new ArrayList<Entry<Key, Type>>();
		}
		
		Type oldValue = remove(key);
		
		entryList.add(new CacheEntry(key, value));
		return oldValue;
	}

	@Override
	public synchronized Type get(Object key) {
		Type result = null;
		if((result = super.get(key)) == null) {
			remove(key);
		}
		return result;
	}

	@Override
	public synchronized boolean containsKey(Object key) {
		if (super.containsKey(key)) {
			if(get(key) != null) {
				return true;
			} else {
				remove(key);
			}
//			return get(key) != null;
		}
		return false;
	}

}
